package com.ylr.filter;



import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Set;



//检查token的过滤器

/**
 *  ServerWebExchange是一个HTTP请求-响应交互的契约。提供对HTTP请求和响应的访问，并公开额外的服务器端处理相关属性和特性，如请求属性。
 * 其实，ServerWebExchange命名为服务网络交换器，存放着重要的请求-响应属性、请求实例和响应实例等等，有点像Context的角色。
 *
 * Ordered中的int getOrder()方法是来给过滤器设定优先级别的，值越大则优先级越低 拦截器的顺序，不多说
 */
@Component
public class TokenCheckFilter implements GlobalFilter, Ordered {

    @Autowired
    private StringRedisTemplate redisTemplate ;


    //这个集合的url是不需要token的url
    @Value("${no.require.urls:/admin/login,/user/gt/register,/user/login,/user/users/register,/user/sms/sendTo,/user/users/setPassword}")
    private Set<String> noRequireTokenUris ;
    /**
     *实现判断用户是否携带token ，或token 错误的功能
     * @param exchange
     * 约定一个HTTP请求-响应交互。提供对HTTP请求和响应的访问，还公开其他服务器端处理相关的属性和特性，如请求属性
     * @param chain
     * 契约允许{@link}委托给链中的下一个。
     * 委托给链中的下一个{@code WebFilter}。当前服务器的交换@return fecode MonoVoid}指示请求处理何时完成
     * @return
     *
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.判断接口是否需要token 才能访问  不需要放行
        // 不需要token 就能访问     exchange相当于一次http请求
        if(!isRequireToken(exchange)){
            return chain.filter(exchange) ;// 不需要token ,直接放行给下一个过滤器链

        }

        //2.取出用户token
        String token = getUserToken(exchange) ;
        //3. 判断token是否有效
        if(StringUtils.isEmpty(token)){  //token是否为空
            return buildeNoAuthorizationResult(exchange) ;
        }
        Boolean hasKey = redisTemplate.hasKey(token);//redis中token是否过期
        if(hasKey!=null && hasKey){
            return chain.filter(exchange) ;// token有效 ,直接放行
        }
        return buildeNoAuthorizationResult(exchange) ;

    }

    /**
     * 给用户响应一个没有token的错误
     * @param exchange
     * @return
     */
    private Mono<Void> buildeNoAuthorizationResult(ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        response.getHeaders().set("Content-Type","application/json");
        response.setStatusCode(HttpStatus.UNAUTHORIZED) ;//未认证

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("error","NoAuthorization") ;
        jsonObject.put("errorMsg","Token is Null or Error") ;
        //？这是什么？
        /**
         *  response.bufferFactory()返回一个f@link DataBufferFactory}，可以用来创建body。返回一个缓冲区工厂@see # writewith (Publisher)
         *
         *  response.bufferFactory().wrap()将给定的{@Link ByteBuffer}封装到{@code DataBuffer}中。不像* @linkplain #alLocateBuffer(int) allocated}，包装不使用新的内存。* eparam byteBuffer NIo字节缓冲区的包装* /
         */
        DataBuffer wrap = response.bufferFactory().wrap(jsonObject.toJSONString().getBytes());
        return response.writeWith(Flux.just(wrap)) ;//响应式编程流数据，写入数据
    }

    /**
     * 从 请求头里面获取用户的token
     * @param exchange
     * @return
     */
    private String getUserToken(ServerWebExchange exchange) {
        // 获取AUTHORIZATION
        //
        //拿到tokenAuthorization字段中JWT_TOKEN。  Authorization: Bearer JWT_TOKEN
        String token = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        return token ==null ? null : token.replace("bearer ","") ;
    }

    /**
     * 判断该 接口是否需要token
     * @param exchange
     * @return
     */
    private boolean isRequireToken(ServerWebExchange exchange) {
        String path = exchange.getRequest().getURI().getPath();
        if(noRequireTokenUris.contains(path)){
            return false ; // 不需要token
        }
        return Boolean.TRUE ;
    }

    /**
     *拦截器的顺序 值越大则优先级越低
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}
